//
//  Invocation.swift
//  MacDemo
//
//  Created by FH on 2020/11/19.
//  Copyright © 2020 FH. All rights reserved.
//

import Foundation

@objc protocol NSMethodSignaturePrivate {
    static func signature(objCTypes: UnsafePointer<CChar>) -> NSMethodSignaturePrivate?
    var numberOfArguments: Int { get }
    @objc(getArgumentTypeAtIndex:) func getArgumentType(at index: Int) -> UnsafePointer<CChar>
    var frameLength: Int { get }
    var isOneway: ObjCBool { get }
    var methodReturnType: UnsafePointer<CChar> { get }
    var methodReturnLength: Int { get }
}

@objc protocol NSInvocationPrivate {
    static func invocation(methodSignature: NSMethodSignaturePrivate) -> NSInvocationPrivate
    var methodSignature: NSMethodSignaturePrivate { get }
    func retainArguments()
    var argumentsRetained: ObjCBool { get }
    var target: AnyObject { get set }
    var selector: Selector { get set }
    func getReturnValue(_ location: UnsafeMutableRawPointer)
    func setReturnValue(_ location: UnsafeRawPointer)
    @objc(getArgument:atIndex:) func getArgument(_ location: UnsafeMutableRawPointer, at index: Int)
    @objc(setArgument:atIndex:) func setArgument(_ location: UnsafeRawPointer, at index: Int)
    func invoke()
    func invoke(target: AnyObject)
}

@objc protocol NSObjectPrivate {
    @objc(methodSignatureForSelector:) func methodSignature(for selector: Selector) -> NSMethodSignaturePrivate?
    func forwardInvocation(_ invocation: NSInvocationPrivate)
}

// MARK: - Import class helper
func importClass<ProtocolType>(_ className: String) -> ProtocolType {
    let typeNameSuffix = ".Type"

    var protocolTypeName = String(reflecting: ProtocolType.self)
    if protocolTypeName.hasPrefix("__C.") {
        protocolTypeName = String(protocolTypeName.suffix(protocolTypeName.count - 4)) + typeNameSuffix
    }
    guard protocolTypeName.hasSuffix(typeNameSuffix) else {
        preconditionFailure("Type `\(protocolTypeName)` is not a protocol type.")
    }

    let protocolName = protocolTypeName.dropLast(typeNameSuffix.count)
    guard let `protocol` = protocolName.withCString(objc_getProtocol) else {
        preconditionFailure("Type `\(protocolName)` is not an objc protocol.")
    }

    guard let `class` = className.withCString(objc_getClass) as? AnyClass else {
        preconditionFailure("Class `\(className)` not found.")
    }

    if !class_addProtocol(`class`, `protocol`) {
        assertionFailure("Failed to attach protocol `\(protocolName)` to class `\(className)`.")
    }

    guard let result = `class` as? ProtocolType else {
        fatalError("Failed to cast class `\(className)` to protocol `\(protocolName)`.")
    }

    return result
}
